﻿using System;
using System.Linq;
using System.Linq.Dynamic;
using System.Collections.Generic;
using HashFoo.Rest.Common;

namespace HashFoo.Rest.Server
{
    /// <summary>
    /// Helper functions to apply <see cref="QueryMeta"/> definitions to queryable result sets.
    /// </summary>
    public static class QueryMetaExecutor
    {
        /// <summary>
        /// Apply a <see cref="QueryMeta"/> definition to a result set.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="meta"></param>
        /// <param name="resultSet"></param>
        /// <returns></returns>
        public static IQueryable<T> ApplyTo<T>(this QueryMeta meta, IQueryable<T> resultSet)
        {
            return ApplyInternal(meta, resultSet).Cast<T>().AsQueryable();
        }

        /// <summary>
        /// Apply a <see cref="QueryMeta"/> definition to a result set.
        /// </summary>
        /// <param name="meta"></param>
        /// <param name="resultSet"></param>
        /// <returns></returns>
        public static IQueryable ApplyTo(this QueryMeta meta, IQueryable resultSet)
        {
            return ApplyInternal(meta, resultSet);
        }

        static IQueryable ApplyInternal(QueryMeta meta, IQueryable resultSet)
        {
            resultSet =
                meta.Filter != null
                    ? resultSet.Where(meta.GetWhereClause(), meta.Filter.Filters.Select(f => f.Value).ToArray())
                    : resultSet;

            resultSet =
                meta.SortDescriptors != null
                    ? resultSet.OrderBy(meta.GetOrderByClause())
                    : resultSet;

            resultSet =
                meta.Skip > 0
                    ? resultSet.Skip(meta.Skip)
                    : resultSet;

            resultSet =
                meta.Take > 0
                    ? resultSet.Take(meta.Take)
                    : resultSet;

            return resultSet;
        }

        /// <summary>
        /// Builds a dynamic linq parsable parameterized where clause from a <see cref="QueryMeta"/> definition.
        /// It is the callers responsibility to provide the parameter array.
        /// </summary>
        /// <param name="meta"></param>
        /// <returns></returns>
        public static string GetWhereClause(this QueryMeta meta)
        {
            var filterSet = meta.Filter;
            var segments = filterSet.Filters.Select((f, i) => string.Format("{0} {1} @{2}", f.PropertyName, FilterOpTokens[f.Op], i));

            var binaryOpToken = BinaryOpTokens[filterSet.BinaryOp];
            var whereClause = segments.Aggregate((c, n) => c + " " + binaryOpToken + " " + n);
            return whereClause;
        }

        /// <summary>
        /// Builds a dynamic linq parsable order by clause form a <see cref="QueryMeta"/> instance.
        /// </summary>
        /// <param name="meta"></param>
        /// <returns></returns>
        public static string GetOrderByClause(this QueryMeta meta)
        {
            var orderByClause =
                meta.SortDescriptors
                    .Select(descriptor => string.Format("{0} {1}", descriptor.PropertyName, SortDirectionTokens[descriptor.Direction]))
                    .Aggregate((c, n) => c + ", " + n);

            return orderByClause;
        }

        /// <summary>
        /// Defines the string representation of a <see cref="FilterOp"/>.
        /// </summary>
        public static readonly IDictionary<FilterOp, string> FilterOpTokens =
            new Dictionary<FilterOp, string>
            {
                { FilterOp.Eq, "==" },
                { FilterOp.Gt, ">" },
                { FilterOp.Gte, ">=" },
                { FilterOp.Lt, "<" },
                { FilterOp.Lte, "<=" },
                {FilterOp.Neq, "!=" }
            };

        /// <summary>
        /// Defines the string representation of a <see cref="FilterBinaryOp"/>.
        /// </summary>
        public static readonly IDictionary<FilterBinaryOp, string> BinaryOpTokens =
            new Dictionary<FilterBinaryOp, string>
            {
                { FilterBinaryOp.And, "and" },
                { FilterBinaryOp.Or, "or" }
            };

        /// <summary>
        /// Defines the string representation of a <see cref="SortDirection"/>.
        /// </summary>
        public static readonly IDictionary<SortDirection, string> SortDirectionTokens =
            new Dictionary<SortDirection, string>
            {
                { SortDirection.Ascending, "asc"},
                {SortDirection.Descending, "desc"}
            };
    }
}